import json
import sys
import time
import traceback
from typing import Literal

from tencentcloud.common.exception import TencentCloudSDKException
from tencentcloud.cvm.v20170312.cvm_client import CvmClient
from tencentcloud.cvm.v20170312.models import DescribeInstancesRequest, Instance, RunInstancesRequest, \
    TerminateInstancesRequest


def create_instance(
    client: CvmClient,
    request_dict: dict,
    instance_name: str | None = None,
) -> str:
    if instance_name is not None:
        request_dict['InstanceName'] = instance_name

    request = RunInstancesRequest()
    request.from_json_string(json.dumps(request_dict))

    response = client.RunInstances(request)

    return response.InstanceIdSet[0]


def describe_instance(
    client: CvmClient,
    instance_id: str,
) -> Instance:
    request = DescribeInstancesRequest()
    request.InstanceIds = [instance_id]

    response = client.DescribeInstances(request)

    instances = response.InstanceSet
    return instances[0] if instances else None


def get_instance_state(
    client: CvmClient,
    instance_id: str,
) -> Literal[
         'PENDING', 'LAUNCH_FAILED', 'RUNNING', 'STOPPED', 'STARTING',
         'STOPPING', 'REBOOTING', 'SHUTDOWN', 'TERMINATING'] | None:
    # 取值范围：
    # PENDING：表示创建中
    # LAUNCH_FAILED：表示创建失败
    # RUNNING：表示运行中
    # STOPPED：表示关机
    # STARTING：表示开机中
    # STOPPING：表示关机中
    # REBOOTING：表示重启中
    # SHUTDOWN：表示停止待销毁
    # TERMINATING：表示销毁中
    instance = describe_instance(client, instance_id)
    return instance.InstanceState if instance else None


def terminate_instance(
    client: CvmClient,
    instance_id: str, *,
    initial_delay: float = 3,
    retry_interval: float = 3,
):
    request = TerminateInstancesRequest()
    request.InstanceIds = [instance_id]

    print(f"Terminating instance {instance_id}...", file=sys.stderr)
    time.sleep(initial_delay)
    while True:
        instance_state = get_instance_state(client, instance_id)
        if instance_state is None:
            print(f"Instance terminated.", file=sys.stderr)
            return
        if instance_state in ['SHUTDOWN', 'TERMINATING']:
            print(f"Instance is already in state {instance_state}.", file=sys.stderr)
            return

        try:
            client.TerminateInstances(request)
        except TencentCloudSDKException as err:
            if err.code == 'InvalidInstanceId.NotFound':
                continue
            traceback.print_exc(file=sys.stderr)
            print(f"Retrying after {retry_interval}s", file=sys.stderr)
            time.sleep(retry_interval)
            print("Retrying...", file=sys.stderr)


def get_instance_ip_or_none(
    client: CvmClient,
    instance_id: str, *,
    network: Literal['public', 'private']
) -> str | None:
    instance = describe_instance(client, instance_id)
    if not instance:
        return None

    if network == 'public':
        ip_list = instance.PublicIpAddresses
    elif network == 'private':
        ip_list = instance.PrivateIpAddresses
    else:
        raise ValueError(f"Invalid network {network=}")

    return ip_list[0] if ip_list else None


def get_instance_ip_or_retry(
    client: CvmClient,
    instance_id: str, *,
    network: Literal['public', 'private'],
    initial_delay: float = 3,
    retry_interval: float = 3
) -> str:
    ip = None
    time.sleep(initial_delay)
    while ip is None:
        time.sleep(retry_interval)
        ip = get_instance_ip_or_none(client, instance_id, network=network)
    return ip
